/** * 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 #include #include #include #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 } #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.