652 lines
14 KiB
C++
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.
|