-- Channel service DB SQL file for PostgreSQL.
-- ChangeLog:
+-- 2012-05-25: MrBean
+-- Added 'totp_key' colum to 'users' table
-- 2011-12-12: Spike
-- Added gline and whitelist tables.
-- 2006-08-10: nighty
signup_ts INT4,
signup_ip VARCHAR(15),
maxlogins INT4 DEFAULT 1,
-
+ totp_key VARCHAR(60) DEFAULT '',
PRIMARY KEY ( id )
) ;
#include <string>
#include <cassert>
-
#include "StringTokenizer.h"
const char rcsId[] = "$Id: StringTokenizer.cc,v 1.7 2005/02/20 15:49:21 dan_karrels Exp $" ;
* Assemble into a string all tokens starting from index
* (start).
*/
-string StringTokenizer::assemble( const size_type& start ) const
+string StringTokenizer::assemble( const size_type& start, int end ) const
{
+
// check if the beginning index is valid
if( !validSubscript( start ) )
{
return string() ;
}
+if(end < 0)
+ {
+ end = size();
+ }
+else if(!validSubscript(end-1))
+ {
+ return string();
+ }
+
// retMe will be returned at the end of the method
string retMe ;
retMe.reserve( totalChars() ) ;
// continue while there are more tokens to concatenate
-for( size_type i = start ; i < size() ; i++ )
+for( size_type i = start ; i < end ; i++ )
{
// Add this token to the returning string
retMe += array[ i ] ;
// If there is another token, put a delimiter here
- if( (i + 1) < size() )
+ if( (i + 1) < end )
{
retMe += delimiter ;
}
* With no argument supplied, assemble() will return the entire
* original string, delimiters included.
*/
- std::string assemble( const size_type& = 0 ) const ;
+ std::string assemble( const size_type& = 0, int = -1 ) const ;
/**
* The immutable iterator type to use for walking through
flagsSet+= "INVITE ";
}
+ if (theUser->getFlag(sqlUser::F_TOTP_ENABLED))
+ flagsSet += "TOTP ";
+
}
/* set 'NONE' if no flags */
#include "cservice_config.h"
#include "Network.h"
#include "ip.h"
+#ifdef HAVE_LIBOATH
+extern "C" {
+#include <liboath/oath.h>
+}
+#endif
const char LOGINCommand_cc_rcsId[] = "$Id: LOGINCommand.cc,v 1.67 2009/06/09 15:40:29 mrbean_ Exp $" ;
st[1].c_str());
return false;
}
+int pass_end = st.size();
+
+#ifdef TOTP_AUTH_ENABLED
+bool totp_enabled = false;
+if(bot->totpAuthEnabled && theUser->getFlag(sqlUser::F_TOTP_ENABLED)) {
+ if(st.size() < 4) {
+ bot->Notice(theClient,"AUTHENTICATION FAILED as %s. (Missing TOTP token)",st[1].c_str());
+ return false;
+ }
+ pass_end = st.size()-1;
+ totp_enabled = true;
+}
+#endif
/*
* Check password, if its wrong, bye bye.
/* if it's not configured, default to every 15 minutes */
if (failed_login_rate==0)
failed_login_rate = 900;
-
-if (!bot->isPasswordRight(theUser, st.assemble(2)))
+if (!bot->isPasswordRight(theUser, st.assemble(2,pass_end)))
{
bot->setFailedLogins(theClient, failedLogins+1);
bot->Notice(theClient,
}
return false;
}
+#ifdef TOTP_AUTH_ENABLED
+if(totp_enabled) {
+ const char* token = st[st.size()-1].c_str();
+ int res=oath_totp_validate(theUser->getTotpKey().c_str(),theUser->getTotpKey().size(),time(NULL),30,0,1,token);
+ if(res < 0 ) {
+ bot->setFailedLogins(theClient, failedLogins+1);
+ bot->Notice(theClient,
+ bot->getResponse(theUser,
+ language::auth_failed_token,
+ string("AUTHENTICATION FAILED as %s. (Invalid Token)")).c_str(),
+ theUser->getUserName().c_str());
+ /* increment failed logins counter */
+ theUser->incFailedLogins();
+ return false;
+ }
+}
+#endif
/*
* Check if this is a privileged user, if so check against IP restrictions
/*
* See if they have any notes.
*/
-
#ifdef USE_NOTES
if(!theUser->getFlag(sqlUser::F_NONOTES))
- {
- stringstream noteQuery;
- noteQuery << "SELECT message_id FROM notes "
- << "WHERE user_id = "
- << theUser->getID()
- << ends;
+ {
+ stringstream noteQuery;
+ noteQuery << "SELECT message_id FROM notes "
+ << "WHERE user_id = "
+ << theUser->getID()
+ << ends;
#ifdef LOG_SQL
- elog << "LOGIN::sqlQuery> "
- << noteQuery.str().c_str()
- << endl;
+ elog << "LOGIN::sqlQuery> "
+ << noteQuery.str().c_str()
+ << endl;
#endif
- bot->SQLDb->Exec(noteQuery, true) ;
-
- unsigned int count = bot->SQLDb->Tuples();
- if(count)
- {
- bot->Notice(theClient, "You have %i note(s). To read "
- "them type /msg %s notes read all",
- count,
- bot->getNickName().c_str());
- }
- }
+ bot->SQLDb->Exec(noteQuery, true) ;
+
+ unsigned int count = bot->SQLDb->Tuples();
+ if(count)
+ {
+ bot->Notice(theClient, "You have %i note(s). To read "
+ "them type /msg %s notes read all",
+ count,
+ bot->getNickName().c_str());
+ }
+ }
#endif
{
bot->incStat("COMMANDS.MODINFO");
+
StringTokenizer st( Message ) ;
if( st.size() < 4 )
{
return true;
}
-string command = string_upper(st[2]);
-if ((command != "ACCESS") && (command != "AUTOMODE") && (command != "INVITE"))
+string command = string_upper(st[1]) == "TOTP" ? "TOTP" : string_upper(st[2]);
+if ((command != "ACCESS") && (command != "AUTOMODE") && (command != "INVITE") && (command != "TOTP"))
{
Usage(theClient);
return true;
}
-if(command != "INVITE" && st.size() < 5 )
+
+if(command != "INVITE" && command != "TOTP" && st.size() < 5 )
{
Usage(theClient);
return true;
return false;
}
+if (command == "TOTP") {
+ if(bot->getAdminAccessLevel(theUser) < adminlevel::modinfo) {
+ bot->Notice(theClient,
+ bot->getResponse(theUser,
+ language::insuf_access,
+ string("Sorry, you have insufficient access to perform that command.")));
+ return false;
+ }
+ sqlUser* modUser = bot->getUserRecord(st[2]);
+ if(theUser == modUser) {
+ bot->Notice(theClient,"Sorry, you can not disable your own TOTP setting");
+ return false;
+ }
+
+ if(string_upper(st[3]) == "OFF") {
+ if(modUser->getFlag(sqlUser::F_TOTP_ENABLED)) {
+ modUser->removeFlag(sqlUser::F_TOTP_ENABLED);
+ if(!modUser->commit(theClient)) {
+ bot->Notice(theClient,"Failed to disable totp for %s",st[2].c_str());
+ return false;
+ }
+ bot->Notice(theClient,"TOTP Authentication disabled for %s",st[2].c_str());
+ return true;
+ }
+ bot->Notice(theClient,"TOTP Authentication already disabled for %s",st[2].c_str());
+ return false;
+ } else if(string_upper(st[3]) == "ON") {
+ bot->Notice(theClient,"Cannot enable TOTP for other users");
+ return false;
+ }
+ bot->Notice(theClient,"Unknown option %s , valid option is OFF",st[3].c_str());
+ return false;
+}
+
+if(st.size() < 4) {
+ Usage(theClient);
+ return true;
+}
/*
* First, check the channel is registered.
*/
Newest changes at the top of the file:
$Id: RELEASE.NOTES,v 1.31 2003/04/24 20:19:36 gte Exp $
+CMaster 1.5 - 26/05/2012
+ * Added TOTP Authentication support to enable follow this procedure:
+ - compile and install liboath (http://www.nongnu.org/oath-toolkit/)
+ - conigure gnuworld --with-liboath (in case it is not installed to the default location use --with-liboath-include and --with-liboath-lib to point to the right directory)
+ - edit mod.cservice/cservice_config.h and change #undef TOTP_AUTH_ENABLED to #define TOTP_AUTH_ENABLED
+ - compile gnuworld
+ - import doc/update.totp_key.sql into the cservice db (psql cservice < doc/update.totp_key.sql)
+ - edit bin/cservice.conf and add the following var: enable_totp = 1
+ - start gnuworld normally
+ Once enabled * and opers will be able to /msg x set totp on , which would enable totp for their account
+ To disable another user with >=800 access will need to /msg x modinfo totp <user> off
+
CMaster 1.4.1pl1 - 14/04/2012
* Fixed bug - X complaining about being deoped when joining a channel
* Removed the need for an admin to FORCE before being able to SET COMMENT on a channel.
*/
#include <string>
+#include <sstream>
#include "StringTokenizer.h"
#include "cservice.h"
#include "levels.h"
#include "responses.h"
#include "cservice_config.h"
-
+#ifdef HAVE_LIBOATH
+extern "C" {
+#include <liboath/oath.h>
+}
+#endif
const char SETCommand_cc_rcsId[] = "$Id: SETCommand.cc,v 1.64 2008/04/16 20:34:44 danielaustin Exp $" ;
namespace gnuworld
return true;
}
+#ifdef TOTP_AUTH_ENABLED
+ if (option == "TOTP")
+ {
+ int admin = bot->getAdminAccessLevel(theUser);
+ if((admin > 0) || (theUser->getFlag(sqlUser::F_OPER)) || (theClient->isOper())) {
+ if(value == "ON") {
+ if(theUser->getFlag(sqlUser::F_TOTP_ENABLED)) {
+ bot->Notice(theClient,"TOTP is already enabled for your account");
+ return true;
+ }
+ if(st.size() == 3) {
+ bot->Notice(theClient,"WARNING: This will enable time-based OTP (one time passwords). Once enabled, in order to login you will require a device to generate the OTP token which has the stored secret key. If you are sure, type: /msg X set totp ON -force");
+ return true;
+ }
+ if(string_upper(st[3]) == "-FORCE") {
+ //Create a random hex stringi 168bit long
+ static char hex_chars[] = "1234567890abcdef";
+ srand(clock()*745);
+ std::stringstream s;
+ for(int i=0; i < 40; i++) {
+ s << hex_chars[rand() % 16];
+ }
+ char* key;
+ int res = oath_base32_encode(s.str().c_str(),s.str().size(),&key,NULL);
+ if(res != OATH_OK) {
+ bot->Notice(theClient,"Failed to enable TOTP authentication, please contact a cservice representitive");
+ return true;
+ }
+ theUser->setTotpKey(s.str());
+ theUser->setFlag(sqlUser::F_TOTP_ENABLED);
+ if(!theUser->commit(theClient)) {
+ bot->Notice(theClient,"Failed to enable TOTP authentication, please contact a cservice representitive");
+ free(key);
+ return true;
+ }
+
+ bot->Notice(theClient,"TOTP Authentication is ENABLED. Your secret key is: %s",key);
+ bot->Notice(theClient,"For QR representation of your key, visit : https://cservice.undernet.org/genqr.php?code=%s&name=UnderNet",key);
+ bot->Notice(theClient,"Please note, this key will never be presented to you again. NEVER GIVE YOUR KEY TO ANYONE!");
+ free(key);
+ return true;
+ }
+ }
+ }
+ }
+#endif
bot->Notice(theClient,
bot->getResponse(theUser,
language::invalid_option,
* articles of data.
*/
const std::string channel_fields = "id,name,flags,mass_deop_pro,flood_pro,url,description,comment,keywords,registered_ts,channel_ts,channel_mode,userflags,last_updated,limit_offset,limit_period,limit_grace,limit_max,max_bans";
- const std::string user_fields = "users.id,users.user_name,users.password,users.url,users.language_id,users.flags,users.last_updated_by,users.last_updated,users.email,users.maxlogins";
+ const std::string user_fields = "users.id,users.user_name,users.password,users.url,users.language_id,users.flags,users.last_updated_by,users.last_updated,users.email,users.maxlogins,users.totp_key";
const std::string level_fields = "channel_id,user_id,access,flags,suspend_expires,suspend_level,suspend_by,added,added_by,last_Modif,last_Modif_By,last_Updated";
const std::string ban_fields = "id,channel_id,banmask,set_by,set_ts,level,expires,reason,last_updated";
}
"hello_block_period" )->second.c_str() ) ;
#endif // ALLOW_HELLO
+#ifdef TOTP_AUTH_ENABLED
+ totpAuthEnabled = atoi((cserviceConfig->Require( "enable_totp" )->second).c_str()) == 1;
+#endif
+
loadConfigData();
userHits = 0;
xClient::DoCTCP(theClient, CTCP,
"Undernet P10 Channel Services II ["
__DATE__ " " __TIME__
- "] Release 1.4.1pl1");
+ "] Release 1.5.0");
}
else if(Command == "PROBLEM?")
{
helloIPListType helloIPList ;
unsigned int helloBlockPeriod ;
#endif // ALLOW_HELLO
+
+#ifdef TOTP_AUTH_ENABLED
+ bool totpAuthEnabled;
+#endif
} ;
const string escapeSQLChars(const string& theString);
*/
#undef USE_COMMAND_LOG
+/**
+ * Define this if you want TOTP authentication, note you must have liboath
+ * installed, and configure --with-liboath for totp to work
+ */
+#define TOTP_AUTH_ENABLED
+
#endif // __CSERVICE_CONFIG_H
const int auth_failed_logins = 176;
const int max_failed_logins = 177;
const int automode_invite = 178;
+ const int auth_failed_token = 179;
const int greeting = 9998;
const int motd = 9999;
}
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 unsigned int sqlUser::EV_SUSPEND = 1;
const unsigned int sqlUser::EV_UNSUSPEND = 2;
maxlogins = atoi(SQLDb->GetValue(row, 9));
failed_logins = 0;
failed_login_ts = 0;
-
+totp_key = SQLDb->GetValue(row, 10);
/* Fetch the "Last Seen" time from the users_lastseen table. */
}
<< "language_id = " << language_id << ", "
<< "maxlogins = " << maxlogins << ", "
<< "last_updated = now()::abstime::int4, "
- << "last_updated_by = '" << escapeSQLChars(last_updated_by) << "' "
+ << "last_updated_by = '" << escapeSQLChars(last_updated_by) << "', "
+ << "totp_key = '" << escapeSQLChars(totp_key) << "' "
<< queryCondition << id
<< ends;
static const flagType F_ALUMNI;
static const flagType F_OPER;
static const flagType F_NOADDUSER;
+ static const flagType F_TOTP_ENABLED;
/*
* User 'Event' Flags, used in the userlog table.
inline const unsigned int& getLastFailedLoginTS() const
{ return failed_login_ts ; }
+
+ inline const std::string& getTotpKey() const
+ { return totp_key ; }
/*
* Methods to set data atrributes.
inline void setLastFailedLoginTS( const unsigned int& _ts )
{ failed_login_ts = _ts ; }
+ inline void setTotpKey( const std::string& _totp_key )
+ { totp_key = _totp_key ; }
/*
* Method to perform a SQL 'UPDATE' and commit changes to this
* object back to the database.
unsigned int notes_sent;
unsigned int failed_logins;
unsigned int failed_login_ts;
+ std::string totp_key;
dbHandle* SQLDb;
} ;