diff --git a/src/common/config.py b/src/common/config.py index d4d6a7bac..884da1df5 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -247,6 +247,7 @@ class Config: 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')], 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')], 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')], + 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')], 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')], 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')], 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')], diff --git a/src/common/kwalletbinding.py b/src/common/kwalletbinding.py new file mode 100644 index 000000000..a2562352c --- /dev/null +++ b/src/common/kwalletbinding.py @@ -0,0 +1,75 @@ +# -*- coding:utf-8 -*- +## src/common/kwalletbinding.py +## +## Copyright (c) 2009 Thorsten Glaser +## +## This file is part of Gajim. +## +## Gajim 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; version 3 only. This file is +## also available under the terms of The MirOS Licence. +## +## Gajim 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 Gajim. If not, see . +## + +__all__ = ['kwallet_available', 'kwallet_get', 'kwallet_put'] + +import subprocess + + +def kwallet_available(): + """Return True if kwalletcli can be run, False otherwise.""" + p = subprocess.Popen(["kwalletcli", "-qV"]) + p.communicate() + if p.returncode == 0: + return True + return False + + +def kwallet_get(folder, entry): + """Retrieve a passphrase from the KDE Wallet via kwalletcli. + + Arguments: + • folder: The top-level category to use (normally the programme name) + • entry: The key of the entry to retrieve + + Returns the passphrase as unicode, False if it cannot be found, + or None if an error occured. + + """ + p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), + "-e", entry.encode('utf-8')], stdout=subprocess.PIPE) + pw = p.communicate()[0] + if p.returncode == 0: + return unicode(pw.decode('utf-8')) + if p.returncode == 1 or p.returncode == 4: + # ENOENT + return False + # error + return None + + +def kwallet_put(folder, entry, passphrase): + """Store a passphrase into the KDE Wallet via kwalletcli. + + Arguments: + • folder: The top-level category to use (normally the programme name) + • entry: The key of the entry to store + • passphrase: The value to store + + Returns True on success, False otherwise. + + """ + p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), + "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE) + p.communicate(passphrase.encode('utf-8')) + if p.returncode == 0: + return True + return False diff --git a/src/common/passwords.py b/src/common/passwords.py index a236b3c1e..98a7f61d9 100644 --- a/src/common/passwords.py +++ b/src/common/passwords.py @@ -7,6 +7,7 @@ ## Copyright (C) 2007 Jean-Marie Traissard ## Julien Pivotto ## Copyright (C) 2008 Stephan Erb +## Copyright (c) 2009 Thorsten Glaser ## ## This file is part of Gajim. ## @@ -27,9 +28,11 @@ __all__ = ['get_password', 'save_password'] import warnings from common import gajim +from common import kwalletbinding USER_HAS_GNOMEKEYRING = False USER_USES_GNOMEKEYRING = False +USER_HAS_KWALLETCLI = False gnomekeyring = None class PasswordStorage(object): @@ -42,8 +45,11 @@ class PasswordStorage(object): class SimplePasswordStorage(PasswordStorage): def get_password(self, account_name): passwd = gajim.config.get_per('accounts', account_name, 'password') - if passwd and passwd.startswith('gnomekeyring:'): - return None # this is not a real password, it's a gnome keyring token + if passwd and (passwd.startswith('gnomekeyring:') or \ + passwd == ''): + # this is not a real password, it's either a gnome + # keyring token or stored in the KDE wallet + return None else: return passwd @@ -65,7 +71,7 @@ class GnomePasswordStorage(PasswordStorage): def get_password(self, account_name): conf = gajim.config.get_per('accounts', account_name, 'password') - if conf is None: + if conf is None or conf == '': return None if not conf.startswith('gnomekeyring:'): password = conf @@ -129,6 +135,45 @@ class GnomePasswordStorage(PasswordStorage): if account_name in gajim.connections: gajim.connections[account_name].password = password +class KWalletPasswordStorage(PasswordStorage): + def get_password(self, account_name): + pw = gajim.config.get_per('accounts', account_name, 'password') + if not pw or pw.startswith('gnomekeyring:'): + # unset, empty or not ours + return None + if pw != '': + # migrate the password + if kwalletbinding.kwallet_put('gajim', account_name, pw): + gajim.config.set_per('accounts', account_name, 'password', + '') + else: + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + return pw + pw = kwalletbinding.kwallet_get('gajim', account_name) + if pw is None: + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + if not pw: + # False, None, or the empty string + return None + return pw + + def save_password(self, account_name, password): + if not kwalletbinding.kwallet_put('gajim', account_name, password): + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + storage.save_password(account_name, password) + return + pwtoken = '' + if not password: + # no sense in looking up the empty string in the KWallet + pwtoken = '' + gajim.config.set_per('accounts', account_name, 'password', pwtoken) + if account_name in gajim.connections: + gajim.connections[account_name].password = password + + storage = None def get_storage(): global storage @@ -150,11 +195,16 @@ def get_storage(): if USER_USES_GNOMEKEYRING: try: storage = GnomePasswordStorage() - except gnomekeyring.NoKeyringDaemonError: - storage = SimplePasswordStorage() - except gnomekeyring.DeniedError: - storage = SimplePasswordStorage() - else: + except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError): + storage = None + if storage is None: + if gajim.config.get('use_kwalletcli'): + global USER_HAS_KWALLETCLI + if kwalletbinding.kwallet_available(): + USER_HAS_KWALLETCLI = True + if USER_HAS_KWALLETCLI: + storage = KWalletPasswordStorage() + if storage is None: storage = SimplePasswordStorage() return storage diff --git a/src/features_window.py b/src/features_window.py index e402339d1..ebe001558 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -30,6 +30,7 @@ import gtkgui_helpers from common import gajim from common import helpers +from common import kwalletbinding class FeaturesWindow: '''Class for features window''' @@ -66,9 +67,9 @@ class FeaturesWindow: _('Gajim session is stored on logout and restored on login.'), _('Requires python-gnome2.'), _('Feature not available under Windows.')), - _('Password encryption'): (self.gnome_keyring_available, + _('Password encryption'): (self.some_keyring_available, _('Passwords can be stored securely and not just in plaintext.'), - _('Requires gnome-keyring and python-gnome2-desktop.'), + _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'), _('Feature not available under Windows.')), _('SRV'): (self.srv_available, _('Ability to connect to servers which are using SRV records.'), @@ -200,9 +201,11 @@ class FeaturesWindow: return False return True - def gnome_keyring_available(self): + def some_keyring_available(self): if os.name == 'nt': return False + if kwalletbinding.kwallet_available(): + return True try: import gnomekeyring except Exception: