gnuworld/mod.cservice/sqlUser.cc

652 lines
14 KiB
C++

/**
* sqlUser.cc
*
* Storage class for accessing user information either from the backend
* or internal storage.
*
* 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.
*
* $Id: sqlUser.cc,v 1.49 2012/05/28 20:40:28 Seven Exp $
*/
#include <sstream>
#include <string>
#include <iostream>
#include <cstring>
#include "ELog.h"
#include "misc.h"
#include "sqlUser.h"
#include "constants.h"
#include "cservice.h"
#include "cservice_config.h"
#ifdef HAVE_LIBOATH
extern "C"
{
#include <liboath/oath.h>
}
#endif
namespace gnuworld
{
using std::string ;
using std::endl ;
using std::ends ;
using std::stringstream ;
const sqlUser::flagType sqlUser::F_GLOBAL_SUSPEND = 0x01 ;
const sqlUser::flagType sqlUser::F_LOGGEDIN = 0x02 ;
const sqlUser::flagType sqlUser::F_INVIS = 0x04 ;
const sqlUser::flagType sqlUser::F_FRAUD = 0x08 ;
const sqlUser::flagType sqlUser::F_NONOTES = 0x10 ;
const sqlUser::flagType sqlUser::F_NOPURGE = 0x20 ;
const sqlUser::flagType sqlUser::F_NOADMIN = 0x40 ;
const sqlUser::flagType sqlUser::F_ALUMNI = 0x80 ;
const sqlUser::flagType sqlUser::F_OPER = 0x100 ;
const sqlUser::flagType sqlUser::F_NOADDUSER = 0x200 ;
const sqlUser::flagType sqlUser::F_TOTP_ENABLED = 0x400;
const sqlUser::flagType sqlUser::F_AUTONICK = 0x800;
const sqlUser::flagType sqlUser::F_POWER = 0x1000;
const unsigned int sqlUser::EV_SUSPEND = 1;
const unsigned int sqlUser::EV_UNSUSPEND = 2;
const unsigned int sqlUser::EV_ADMINMOD = 3;
const unsigned int sqlUser::EV_MISC = 4;
const unsigned int sqlUser::EV_COMMENT = 5;
sqlUser::sqlUser(dbHandle* _SQLDb)
: id( 0 ),
user_name(),
password(),
last_seen( 0 ),
url(),
nickname(),
language_id( 0 ),
flags( 0 ),
last_used( 0 ),
created_ts(0),
instantiated_ts( ::time(NULL) ),
signup_ip(),
email(),
verifNr(0),
verifdata(),
last_hostmask(),
maxlogins(0),
last_note(0),
notes_sent(0),
failed_logins(0),
failed_login_ts(0),
totp_key(),
totp_hex_key(),
hostname(),
SQLDb( _SQLDb )
{
}
/*
* Load all data for this user from the backend. (Key: userID)
*/
bool sqlUser::loadData(int userID)
{
/*
* With the open database handle 'SQLDb', retrieve information about
* 'userID' and fill our member variables.
*/
#ifdef LOG_DEBUG
elog << "sqlUser::loadData> Attempting to load data for user-id: "
<< userID
<< endl;
#endif
stringstream queryString;
queryString << "SELECT "
<< sql::user_fields
<< " FROM users WHERE id = "
<< userID
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::loadData> "
<< queryString.str().c_str()
<< endl;
#endif
if( SQLDb->Exec(queryString, true ) )
//if( PGRES_TUPLES_OK == status )
{
/*
* If the user doesn't exist, we won't get any rows back.
*/
if(SQLDb->Tuples() < 1)
{
return (false);
}
setAllMembers(0);
return (true);
}
return (false);
}
bool sqlUser::loadData(const string& userName)
{
/*
* With the open database handle 'SQLDb', retrieve information about
* 'userID' and fill our member variables.
*/
#ifdef LOG_DEBUG
elog << "sqlUser::loadData> Attempting to load data for user-name: "
<< userName
<< endl;
#endif
stringstream queryString;
queryString << "SELECT "
<< sql::user_fields
<< " FROM users WHERE lower(user_name) = '"
<< escapeSQLChars(string_lower(userName))
<< "'"
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::loadData> "
<< queryString.str().c_str()
<< endl;
#endif
if( SQLDb->Exec(queryString, true ) )
//if( PGRES_TUPLES_OK == status )
{
/*
* If the user doesn't exist, we won't get any rows back.
*/
if(SQLDb->Tuples() < 1)
{
return (false);
}
setAllMembers(0);
return (true);
}
return (false);
}
void sqlUser::setAllMembers(int row)
{
/*
* Support function for both loadData's.
* Assumes SQLDb contains a valid results set for all user information.
*/
id = atoi(SQLDb->GetValue(row, 0));
user_name = SQLDb->GetValue(row, 1);
password = SQLDb->GetValue(row, 2);
url = SQLDb->GetValue(row, 3);
nickname = SQLDb->GetValue(row, 4);
language_id = atoi(SQLDb->GetValue(row, 5));
flags = atoi(SQLDb->GetValue(row, 6));
last_updated_by = SQLDb->GetValue(row, 7);
last_updated = atoi(SQLDb->GetValue(row, 8));
email = SQLDb->GetValue(row, 9);
maxlogins = atoi(SQLDb->GetValue(row, 10));
verifdata = SQLDb->GetValue(row, 11);
totp_key = SQLDb->GetValue(row, 12);
hostname = SQLDb->GetValue(row, 13);
failed_logins = 0;
failed_login_ts = 0;
instantiated_ts = atoi(SQLDb->GetValue(row, 14));
signup_ip = SQLDb->GetValue(row, 15);
created_ts = atoi(SQLDb->GetValue(row, 16));
/* Fetch the "Last Seen" time from the users_lastseen table. */
}
bool sqlUser::commit(iClient* who)
{
/*
* Build an SQL statement to commit the transient data in this storage class
* back into the database.
*/
if(who)
{
last_updated_by = who->getNickUserHost();
} else {
last_updated_by = "Marvin, the paranoid android.";
}
static const char* queryHeader = "UPDATE users ";
static const char* queryCondition = "WHERE id = ";
stringstream queryString;
queryString << queryHeader
<< "SET flags = " << flags << ", "
<< "password = '" << password << "', "
<< "url = '" << url << "', "
<< "nickname = '" << escapeSQLChars(nickname) << "', "
// << "question_id = " << verifNr << ", "
// << "verificationdata = '" << verifdata << "', "
<< "language_id = " << language_id << ", "
<< "maxlogins = " << maxlogins << ", "
<< "last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int, "
<< "last_updated_by = '" << escapeSQLChars(last_updated_by) << "', "
<< "totp_key = '" << escapeSQLChars(totp_key) << "', "
<< "hostname = '" << escapeSQLChars(hostname) << "' "
<< queryCondition << id
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::commit> "
<< queryString.str().c_str()
<< endl;
#endif
if( !SQLDb->Exec(queryString ) )
//if( PGRES_COMMAND_OK != status )
{
// TODO: Log to msgchan here.
elog << "sqlUser::commit> Something went wrong: "
<< SQLDb->ErrorMessage()
<< endl;
return false;
}
return true;
}
bool sqlUser::commitLastSeen()
{
/*
* Build an SQL statement to write the last_seen field to a seperate table.
*/
static const char* queryHeader = "UPDATE users_lastseen ";
static const char* queryCondition = "WHERE user_id = ";
stringstream queryString;
queryString << queryHeader
<< "SET last_seen = "
<< last_seen
<< ", "
<< "last_hostmask = '"
<< escapeSQLChars(last_hostmask)
<< "', "
<< "last_ip = '"
<< escapeSQLChars(last_ip)
<< "', "
<< "last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int "
<< queryCondition
<< id
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::commitLastSeen> "
<< queryString.str().c_str()
<< endl;
#endif
if( !SQLDb->Exec(queryString ) )
//if( PGRES_COMMAND_OK != status )
{
// TODO: Log to msgchan here.
elog << "sqlUser::commit> Something went wrong: "
<< SQLDb->ErrorMessage()
<< endl;
return false;
}
return true;
}
bool sqlUser::commitLastSeenWithoutMask()
{
/*
* -- Boy, we need a masked Commit() method in these classes. ;)
*/
static const char* queryHeader = "UPDATE users_lastseen ";
static const char* queryCondition = "WHERE user_id = ";
stringstream queryString;
queryString << queryHeader
<< "SET last_seen = "
<< last_seen
<< ", last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int "
<< queryCondition
<< id
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::commitLastSeenWithoutMask> "
<< queryString.str().c_str()
<< endl;
#endif
if( !SQLDb->Exec(queryString ) )
//if( PGRES_COMMAND_OK != status )
{
elog << "sqlUser::commit> Something went wrong: "
<< SQLDb->ErrorMessage()
<< endl;
return false;
}
return true;
}
time_t sqlUser::getLastSeen()
{
stringstream queryString;
queryString << "SELECT last_seen"
<< " FROM users_lastseen WHERE user_id = "
<< id
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::getLastSeen> "
<< queryString.str().c_str()
<< endl;
#endif
if( SQLDb->Exec(queryString, true ) )
//if( PGRES_TUPLES_OK == status )
{
/*
* If the user doesn't exist, we won't get any rows back.
*/
if(SQLDb->Tuples() < 1)
{
return 0;
}
last_seen = atoi(SQLDb->GetValue(0, 0));
return (last_seen);
}
return (false);
}
const string sqlUser::getLastHostMask()
{
stringstream queryString;
queryString << "SELECT last_hostmask"
<< " FROM users_lastseen WHERE user_id = "
<< id
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::getLastHostMask> "
<< queryString.str().c_str()
<< endl;
#endif
if( SQLDb->Exec(queryString, true ) )
//if( PGRES_TUPLES_OK == status )
{
/*
* If the user doesn't exist, we won't get any rows back.
*/
if(SQLDb->Tuples() < 1)
{
return ("");
}
last_hostmask = SQLDb->GetValue(0, 0);
return (last_hostmask);
}
return ("");
}
const string sqlUser::getLastIP()
{
stringstream queryString;
queryString << "SELECT last_ip"
<< " FROM users_lastseen WHERE user_id = "
<< id
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::getLastIP> "
<< queryString.str().c_str()
<< endl;
#endif
if( SQLDb->Exec(queryString, true ) )
//if( PGRES_TUPLES_OK == status )
{
/*
* If the user doesn't exist, we won't get any rows back.
*/
if(SQLDb->Tuples() < 1)
{
return ("");
}
last_ip = SQLDb->GetValue(0, 0);
return (last_ip);
}
return ("");
}
void sqlUser::writeEvent(unsigned short eventType, sqlUser* theUser, const string& theMessage)
{
string userExtra = theUser ? theUser->getUserName() : "Not Logged In";
stringstream theLog;
theLog << "INSERT INTO userlog (ts, user_id, event, message, "
<< "last_updated) VALUES "
<< "("
<< "date_part('epoch', CURRENT_TIMESTAMP)::int"
<< ", "
<< id
<< ", "
<< eventType
<< ", "
<< "'"
<< escapeSQLChars(theMessage)
<< " (By " << userExtra << ")"
<< "', date_part('epoch', CURRENT_TIMESTAMP)::int)"
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::writeEvent> "
<< theLog.str().c_str()
<< endl;
#endif
// TODO: Is this ok?
SQLDb->Exec(theLog);
//SQLDb->ExecCommandOk(theLog.str().c_str());
}
const string sqlUser::getLastEvent(unsigned short eventType, unsigned int& eventTime)
{
stringstream queryString;
queryString << "SELECT message,ts"
<< " FROM userlog WHERE user_id = "
<< id
<< " AND event = "
<< eventType
<< " ORDER BY ts DESC LIMIT 1"
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::getLastEvent> "
<< queryString.str().c_str()
<< endl;
#endif
if( SQLDb->Exec(queryString, true ) )
//if( PGRES_TUPLES_OK == status )
{
if(SQLDb->Tuples() < 1)
{
return("");
}
string reason = SQLDb->GetValue(0, 0);
eventTime = atoi(SQLDb->GetValue(0, 1));
return (reason);
}
return ("");
}
void sqlUser::generateRecoveryPassword()
{
const char validChars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
setRecoveryPassword(string());
for (unsigned short int i = 0 ; i < 8 ; i++)
{
int randNo = 1 + (int)(62.0*rand()/(RAND_MAX + 1.0));
recovery_password += validChars[randNo];
}
}
bool sqlUser::generateTOTPKey()
{
#ifdef TOTP_AUTH_ENABLED
//Create a random hex string 168bit long
char str_key2[20];
char hex_key[41];
srand(clock()*745+time(NULL));
hex_key[0] = 0;
for(int i=0; i < 20; i++) {
str_key2[i]=((rand() %95) + 32);
sprintf(hex_key+(i*2),"%02x",str_key2[i] & 0xFF);
}
char* key;
int res = oath_base32_encode(str_key2, 20, &key, NULL);
if (res == OATH_OK)
{
totp_key = string(key);
totp_hex_key = string(hex_key);
free(key);
return true;
}
free(key);
#endif
return false;
}
OathResult::OATH_RESULT_TYPE sqlUser::validateTOTP(const string& totp)
{
#ifdef TOTP_AUTH_ENABLED
char* key;
size_t len;
int res = oath_base32_decode(getTotpKey().c_str(), getTotpKey().size(), &key, &len);
if (res != OATH_OK)
{
elog << "ERROR while decoding base32 (" << getTotpKey().c_str() << ") " << oath_strerror(res) << "\n";
free(key);
return OathResult::ERROR;
}
res = oath_totp_validate(key, len, time(NULL), 30, 0, 1, totp.c_str());
free(key);
if (res < 0)
{
return OathResult::INVALID_TOKEN;
}
#endif
return OathResult::OK;
}
sqlUser::~sqlUser()
{
// No heap space allocated
}
bool sqlUser::Insert()
{
/*
* Build an SQL statement to insert the transient data in
* this storage class back into the database.
*/
static const char* queryHeader = "INSERT INTO users "
"(user_name,password,question_id,verificationdata,language_id,flags,last_updated_by,last_"
"updated,email,created_ts,signup_ts,signup_ip) VALUES ('";
stringstream queryString;
queryString << queryHeader
<< escapeSQLChars(user_name)
<< "'," << "'"
<< password << "',"
<< verifNr << ",'"
<< verifdata << "',"
<< 1 << ","
<< 0 << ",'"
<< escapeSQLChars(last_updated_by)
<< "',"
<< "date_part('epoch', CURRENT_TIMESTAMP)::int, '"
<< escapeSQLChars(email)
<< "', "
<< created_ts << ", "
<< instantiated_ts << ", '"
<< signup_ip << "')"
<< ends;
#ifdef LOG_SQL
elog << "sqlUser::insert> "
<< queryString.str()
<< endl;
#endif
if( !SQLDb->Exec(queryString ) )
//if( PGRES_COMMAND_OK != status )
{
// TODO: Log to msgchan here.
elog << "sqlUser::insert> Something went wrong: "
<< SQLDb->ErrorMessage()
<< endl;
return false;
}
return true;
}
} // namespace gnuworld.