From fa67967b096b799d910d19b278576d4225376f7c Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Thu, 2 Aug 2012 09:21:58 +0200 Subject: [PATCH] And commands to add/remove GPG keys. --- plugins/User/plugin.py | 38 +++++++++++++++++++++++ src/gpg.py | 70 ++++++++++++++++++++++++++++++++++++++++++ src/ircdb.py | 7 +++++ 3 files changed, 115 insertions(+) create mode 100644 src/gpg.py diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index e71ae2094..c28681123 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -30,6 +30,7 @@ import re import fnmatch +import supybot.gpg as gpg import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb @@ -382,6 +383,43 @@ class User(callbacks.Plugin): remove = wrap(remove, ['private', 'otherUser', 'something', additional('something', '')]) + class gpg(callbacks.Commands): + def callCommand(self, command, irc, msg, *args, **kwargs): + if gpg.available: + return super(gpg, self) \ + .callCommand(command, irc, msg, *args, **kwargs) + else: + irc.error(_('GPG features are not enabled.')) + + @internationalizeDocstring + def add(self, irc, msg, args, user, keyid, keyserver): + """ + + Add a GPG key to your account.""" + result = gpg.keyring.recv_keys(keyserver, keyid) + count = len(result.fingerprints) + if count: + user.gpgkeys.append(keyid) + irc.reply(format(_('Successful import of %n: %L'), + (count, _('key')), + [x.fingerprint for x in result.results])) + else: + irc.error(_('GPG key not found on the key server.')) + add = wrap(add, ['user', 'somethingWithoutSpaces', + 'somethingWithoutSpaces']) + + @internationalizeDocstring + def remove(self, irc, msg, args, user, keyid): + """ + + Remove a GPG key from your account.""" + try: + user.gpgkeys.remove(keyid) + irc.replySuccess() + except KeyError: + irc.error(_('GPG key not associated with your account.')) + remove = wrap(remove, ['user', 'somethingWithoutSpaces']) + @internationalizeDocstring def capabilities(self, irc, msg, args, user): """[] diff --git a/src/gpg.py b/src/gpg.py new file mode 100644 index 000000000..2305bfc00 --- /dev/null +++ b/src/gpg.py @@ -0,0 +1,70 @@ +### +# Copyright (c) 2012, Valentin Lorentz +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +import os + +import supybot.log as log +import supybot.conf as conf +import supybot.world as world + +try: + import gnupg +except ImportError: + # As we do not want Supybot to depend on GnuPG, we will use it only if + # it is available. Otherwise, we just don't allow user auth through GPG. + log.debug('Cannot import gnupg, using fallback.') + gnupg = None + +available = (gnupg is not None) + +def fallback(default_return=None): + """Decorator. + Does nothing if gnupg is loaded. Otherwise, returns the supplied + default value.""" + def decorator(f): + if available: + def newf(*args, **kwargs): + return f(*args, **kwargs) + else: + def newf(*args, **kwargs): + return default_return + return newf + return decorator + +@fallback() +def loadKeyring(): + global keyring + path = conf.supybot.directories.data.dirize('GPGkeyring') + if not os.path.isdir(path): + os.mkdir(path) + keyring = gnupg.GPG(gnupghome=path) +loadKeyring() + +# Reload the keyring if path changed +conf.supybot.directories.data.addCallback(loadKeyring) diff --git a/src/ircdb.py b/src/ircdb.py index a2b1495bd..201bf4483 100644 --- a/src/ircdb.py +++ b/src/ircdb.py @@ -219,6 +219,7 @@ class IrcUser(object): self.hostmasks = ircutils.IrcSet() # hostmasks used for recognition else: self.hostmasks = hostmasks + self.gpgkeys = [] # GPG key ids def __repr__(self): return format('%s(id=%s, ignore=%s, password="", name=%q, hashed=%r, ' @@ -326,6 +327,8 @@ class IrcUser(object): write('capability %s' % capability) for hostmask in self.hostmasks: write('hostmask %s' % hostmask) + for key in self.gpgkeys: + write('gpgkey %s' % key) fd.write(os.linesep) @@ -499,6 +502,10 @@ class IrcUserCreator(Creator): self._checkId() self.u.capabilities.add(rest) + def gpgkey(self, rest, lineno): + self._checkId() + self.u.gpgkeys.append(rest) + def finish(self): if self.u.name: try: