diff --git a/src/User.py b/src/User.py index 26a63f795..86b790108 100755 --- a/src/User.py +++ b/src/User.py @@ -33,6 +33,7 @@ Provides commands useful to users in general. This plugin is loaded by default. """ +import getopt import string import conf @@ -50,14 +51,20 @@ class User(callbacks.Privmsg): return True def register(self, irc, msg, args): - """ + """[--hashed] Registers with the given password and the current hostmask of the person registering. This command (and all other commands that include a password) must be sent to the bot privately, - not in a channel. + not in a channel. If --hashed is given, the password will be hashed + on disk, rather than being stored in plaintext. """ - (name, password) = privmsgs.getArgs(args, needed=2) + (optlist, rest) = getopt.getopt(args, '', ['hashed']) + (name, password) = privmsgs.getArgs(rest, needed=2) + hashed = False + for (option, arg) in optlist: + if option == '--hashed': + hashed = True if not self._checkNotChannel(irc, msg, password): return try: @@ -77,7 +84,7 @@ class User(callbacks.Privmsg): pass (id, user) = ircdb.users.newUser() user.name = name - user.setPassword(password) + user.setPassword(password, hashed=hashed) user.addHostmask(msg.prefix) ircdb.users.setUser(id, user) irc.reply(msg, conf.replySuccess) @@ -200,13 +207,19 @@ class User(callbacks.Privmsg): return def setpassword(self, irc, msg, args): - """ + """[--hashed] Sets the new password for the user specified by to . Obviously this message must be sent to the bot - privately (not on a channel). + privately (not in a channel). If --hashed is given, the password will + be hashed on disk (rather than being stored in plaintext. """ - (name, oldpassword, newpassword) = privmsgs.getArgs(args, 3) + (optlist, rest) = getopt.getopt(args, '', ['hashed']) + (name, oldpassword, newpassword) = privmsgs.getArgs(rest, 3) + hashed = False + for (option, arg) in optlist: + if option == '--hashed': + hashed = True if not self._checkNotChannel(irc, msg, oldpassword+newpassword): return try: @@ -216,7 +229,7 @@ class User(callbacks.Privmsg): irc.error(msg, conf.replyNoUser) return if user.checkPassword(oldpassword): - user.setPassword(newpassword) + user.setPassword(newpassword, hashed=hashed) ircdb.users.setUser(id, user) irc.reply(msg, conf.replySuccess) else: diff --git a/src/ircdb.py b/src/ircdb.py index bf59103dc..34ce16d12 100644 --- a/src/ircdb.py +++ b/src/ircdb.py @@ -186,11 +186,12 @@ class UserCapabilitySet(CapabilitySet): class IrcUser(object): """This class holds the capabilities and authentications for a user.""" def __init__(self, ignore=False, password='', name='', - capabilities=(), hostmasks=None, secure=False): + capabilities=(), hostmasks=None, secure=False, hashed=False): self.auth = None # The (time, hostmask) a user authenticated under self.name = name # The name of the user. self.ignore = ignore # A boolean deciding if the person is ignored. self.secure = secure # A boolean describing if hostmasks *must* match. + self.hashed = hashed # True if the password is hashed on disk. self.password = password # password (plaintext? hashed?) self.capabilities = UserCapabilitySet() for capability in capabilities: @@ -201,10 +202,11 @@ class IrcUser(object): self.hostmasks = hostmasks def __repr__(self): - return '%s(ignore=%s, password=%r, name=%r, '\ + return '%s(ignore=%s, password=%r, name=%r, hashed=%r, '\ 'capabilities=%r, hostmasks=%r, secure=%r)\n' %\ - (self.__class__.__name__, self.ignore, self.password, - self.name, self.capabilities, self.hostmasks, self.secure) + (self.__class__.__name__, + self.ignore, self.password, self.name, self.hashed, + self.capabilities, self.hostmasks, self.secure) def addCapability(self, capability): """Gives the user the given capability.""" @@ -224,13 +226,21 @@ class IrcUser(object): else: return self.capabilities.check(capability) - def setPassword(self, password): + def setPassword(self, password, hashed=False): """Sets the user's password.""" - self.password = password + if hashed or self.hashed: + self.hashed = True + self.password = utils.saltHash(password) + else: + self.password = password def checkPassword(self, password): """Checks the user's password.""" - return (self.password == password) + if self.hashed: + (salt, _) = self.password.split('|') + return (self.password == utils.saltHash(password, salt=salt)) + else: + return (self.password == password) def checkHostmask(self, hostmask, useAuth=True): """Checks a given hostmask against the user's hostmasks or current diff --git a/src/utils.py b/src/utils.py index bf87ce4d0..43de7d558 100755 --- a/src/utils.py +++ b/src/utils.py @@ -412,7 +412,7 @@ def saltHash(password, salt=None, hash='sha'): hasher = md5.md5 elif hash == 'sha': hasher = sha.sha - return salt + hasher(salt + password).hexdigest() + return '|'.join([salt, hasher(salt + password).hexdigest()]) class IterableMap(object): """Define .iteritems() in a class and subclass this to get the other iters. diff --git a/test/test_User.py b/test/test_User.py index 180f87e7f..95ba533c9 100644 --- a/test/test_User.py +++ b/test/test_User.py @@ -54,5 +54,15 @@ class UserTestCase(PluginTestCase, PluginDocumentation): self.assertError('changeusername foo bar') self.assertNotError('changeusername foo baz') + def testSetpassword(self): + self.prefix = self.prefix1 + self.assertNotError('register foo bar') + self.assertEqual(ircdb.users.getUser(self.prefix).password, 'bar') + self.assertNotError('setpassword foo bar baz') + self.assertEqual(ircdb.users.getUser(self.prefix).password, 'baz') + self.assertNotError('setpassword --hashed foo baz biff') + self.assertNotEqual(ircdb.users.getUser(self.prefix).password, 'biff') + + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_ircdb.py b/test/test_ircdb.py index fcef04b40..5f3b25fc4 100644 --- a/test/test_ircdb.py +++ b/test/test_ircdb.py @@ -212,6 +212,13 @@ class IrcUserTestCase(unittest.TestCase): self.failUnless(u.checkPassword('foobar')) self.failIf(u.checkPassword('somethingelse')) + def testHashedPassword(self): + u = ircdb.IrcUser() + u.setPassword('foobar', hashed=True) + self.failUnless(u.checkPassword('foobar')) + self.failIf(u.checkPassword('somethingelse')) + self.assertNotEqual(u.password, 'foobar') + def testHostmasks(self): prefix = 'foo!bar@baz' hostmasks = ['*!bar@baz', 'foo!*@*'] diff --git a/test/test_utils.py b/test/test_utils.py index 4d3ecc417..aca43ae69 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -277,7 +277,8 @@ class UtilsTest(unittest.TestCase): def testSaltHash(self): s = utils.saltHash('jemfinch') - self.assertEqual(utils.saltHash('jemfinch', salt=s[:8]), s) + (salt, hash) = s.split('|') + self.assertEqual(utils.saltHash('jemfinch', salt=salt), s)