]> jfr.im git - irc/UndernetIRC/gnuworld.git/commitdiff
CMaster 1.5 - 26/05/2012
authorMrBean <redacted>
Sat, 26 May 2012 05:14:32 +0000 (09:14 +0400)
committerMrBean <redacted>
Sat, 26 May 2012 05:14:32 +0000 (09:14 +0400)
 * 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

15 files changed:
doc/cservice.sql
libgnuworld/StringTokenizer.cc
libgnuworld/StringTokenizer.h
mod.cservice/CHANINFOCommand.cc
mod.cservice/LOGINCommand.cc
mod.cservice/MODINFOCommand.cc
mod.cservice/RELEASE.NOTES
mod.cservice/SETCommand.cc
mod.cservice/constants.h
mod.cservice/cservice.cc
mod.cservice/cservice.h
mod.cservice/cservice_config.h
mod.cservice/responses.h
mod.cservice/sqlUser.cc
mod.cservice/sqlUser.h

index 08958d54642539cf6a4ce5dad6bcafa0d6617437..2ca1cfb940ecb43dc0653f869ce5fcf2330e74e5 100644 (file)
@@ -3,6 +3,8 @@
 -- 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
@@ -221,7 +223,7 @@ CREATE TABLE users (
        signup_ts INT4,
        signup_ip VARCHAR(15),
        maxlogins INT4 DEFAULT 1,
-
+       totp_key  VARCHAR(60) DEFAULT '',
        PRIMARY KEY ( id )
 ) ;
 
index 7d0ae8f788422cb2665415c57574e80ed13f34cd..a5bb4fe9d317ca07676dca29b62e67eadc56b236 100644 (file)
@@ -26,7 +26,6 @@
 #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 $" ;
@@ -158,26 +157,36 @@ delete[] addMe ;
  * 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 ;
                }
index 6e7d0f285bddfa256f47e575bd095578693be8ea..c390f6ce21702554c0b99129728210c7429ef4c3 100644 (file)
@@ -129,7 +129,7 @@ public:
         * 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
index a75920fb02755ee8acbee1c19f39d575ddbadab9..79c75bcc5a3f738b2d0d653cd2f21253dfe886c7 100755 (executable)
@@ -197,6 +197,9 @@ if( string::npos == st[ 1 ].find_first_of( '#' ) )
                        flagsSet+= "INVITE ";
                }
 
+               if (theUser->getFlag(sqlUser::F_TOTP_ENABLED))
+                        flagsSet += "TOTP ";
+
 
        }
        /* set 'NONE' if no flags */
index 06f2a55af9be9b1a7f404dfcdc7af5e7f093645f..066c509e2a11efb81dc369b9d2bb6c87c16a7e58 100755 (executable)
 #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 $" ;
 
@@ -140,6 +145,19 @@ if (theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
        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.
@@ -151,8 +169,7 @@ unsigned int failed_login_rate = bot->getConfigVar("FAILED_LOGINS_RATE")->asInt(
 /* 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,
@@ -201,6 +218,23 @@ if (!bot->isPasswordRight(theUser, st.assemble(2)))
        }
        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
@@ -661,34 +695,33 @@ for(unsigned int i = 0; i < bot->SQLDb->Tuples(); i++)
 /*
  * 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
 
index 1a414c7360c30dd6a9783da61d6055ad33dabbeb..7561f6bb11d21eb4d78d9eea83dd47e3caa693dc 100755 (executable)
@@ -48,6 +48,7 @@ bool MODINFOCommand::Exec( iClient* theClient, const string& Message )
 {
 bot->incStat("COMMANDS.MODINFO");
 
+
 StringTokenizer st( Message ) ;
 if( st.size() < 4 )
        {
@@ -55,13 +56,14 @@ 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;
@@ -77,6 +79,44 @@ if (!theUser)
        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.
  */
index a6c1bd716e9eef6b0efa45442993b205cffb5ed5..0c8fc9e27ec43681565a789cf03653673139ea8e 100755 (executable)
@@ -1,6 +1,18 @@
 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.
index 17815a5c832b75eb1e23a9fd58cac735f5fe05d2..5f47bdb9cd05b43dd3daa8a0a285c1745472d219 100755 (executable)
@@ -37,6 +37,7 @@
  */
 
 #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
@@ -262,6 +267,52 @@ if( st[1][0] != '#' ) // Didn't find a hash?
                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,
index dfc4315fd0ca7ef6d0a879da97f9f98a87c57c44..826044ef2f7279cad8204463e73af46d2e0b56ab 100755 (executable)
@@ -38,7 +38,7 @@ namespace sql
         *  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";
        }
index c685ef0c4be8030784086acdfd3f5f035184eaef..1fc1ff187f5d830940751097e786a1aab3c2d6ad 100644 (file)
@@ -352,6 +352,10 @@ commandlogPath = cserviceConfig->Require( "command_logfile" )->second ;
     "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;
@@ -1027,7 +1031,7 @@ else if(Command == "VERSION")
        xClient::DoCTCP(theClient, CTCP,
                "Undernet P10 Channel Services II ["
                __DATE__ " " __TIME__
-               "] Release 1.4.1pl1");
+               "] Release 1.5.0");
        }
 else if(Command == "PROBLEM?")
        {
index d8031904ab612bd5e784703471606593d5f26e1c..c0284e268520dabb7109df3f7ec2cd395cf047cf 100755 (executable)
@@ -571,6 +571,10 @@ public:
        helloIPListType helloIPList ;
        unsigned int helloBlockPeriod ;
 #endif // ALLOW_HELLO
+
+#ifdef TOTP_AUTH_ENABLED
+       bool totpAuthEnabled;
+#endif
 } ;
 
 const string escapeSQLChars(const string& theString);
index 295bc8e3861d7a97058c1301fe2468ab76da318f..95bc2a35cbee6c7e4545e20378933d5a3b73854d 100644 (file)
  */
 #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
index 9803951626656f87c4d4a5b31aae883029bc851f..c08a4e9fe2021635ebfb64d6c63fecc4377f53d7 100755 (executable)
@@ -214,6 +214,7 @@ namespace gnuworld
                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;
        }
index 1038287d7b60cdd578c0ac5b646338e8a8aea7dc..15a1ac9cd34ca1471f9b91b6081b59348c335e2e 100644 (file)
@@ -53,6 +53,7 @@ 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 unsigned int sqlUser::EV_SUSPEND         = 1;
 const unsigned int sqlUser::EV_UNSUSPEND       = 2;
@@ -198,7 +199,7 @@ email = SQLDb->GetValue(row, 8);
 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. */
 
 }
@@ -226,7 +227,8 @@ queryString << queryHeader
                << "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;
 
index a9d42796383889e57b49efc548b738b1e7d4278f..062d32b77662259db7a28bd64c4deded93ab088b 100644 (file)
@@ -52,6 +52,7 @@ public:
        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.
@@ -139,6 +140,9 @@ public:
 
        inline const unsigned int& getLastFailedLoginTS() const
                { return failed_login_ts ; }
+       
+       inline const std::string&       getTotpKey() const
+               { return totp_key ; }
 
        /*
         *  Methods to set data atrributes.
@@ -200,6 +204,8 @@ public:
        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.
@@ -246,6 +252,7 @@ protected:
        unsigned int    notes_sent;
        unsigned int    failed_logins;
        unsigned int    failed_login_ts;
+       std::string     totp_key;
 
        dbHandle*       SQLDb;
 } ;